/*
 * Author: vdaras
 */


#include "../sdl/SDLException.h"
#include "../sdl/TextImage.h"
#include "../sdl/sdl_utilities.h"
#include "../MouseEvent.h"
#include "Button.h"
#include "Exception.h"
#include <iostream>

namespace gui
{


/**
 * Constructor initializes the Abstract Button parent and the caption of the button.
 * The display part of the button is initialized with the default colors.
 *
 * @param name: id of the Button Component
 * @param width: width of the Button Component
 * @param height: height of the Button Component
 * @param caption: the caption of the button
 */

Button::Button(const std::string &name, int width, int height, const std::string &caption, Resource<sdl::Font> *fnt)
:
AbstractButton(name, width, height),
m_caption(caption),
m_fontRes(fnt)
{  
    m_font = m_fontRes->GetRawData( );
    //init button, if initialization fails
    if( !Initialize( ) )
    {
        //notify the caller
        throw Exception( "gui::Button cannot be created!!" );
    }
}


/**
 * Constructor initializes the Abstract Button parent and the caption of the button.
 * The display part of the button is initialized with the default colors.
 *
 * @param name: id of the Button Component
 * @param width: width of the Button Component
 * @param height of the Button Component
 * @Param caption: the caption of the button
 */

Button::Button(const std::string& name, Container* parent, int x, int y, int width, int height, const std::string& caption, Resource<sdl::Font> *fnt)
:
AbstractButton(name, parent, x, y, width, height),
m_caption(caption),
m_fontRes(fnt)
{
    m_font = m_fontRes->GetRawData( );
    //init button, if initialization fails
    if( !Initialize( ) )
    {
        //notify the caller
        throw Exception( "gui::Button cannot be created!!" );
    }
}


Button::~Button()
{
    m_fontRes->DecreaseReference( );
}


/**
 * Resizes this Button.
 *
 * @param width: new width.
 * @param height: new height.
 */

void Button::Resize(int width, int height)
{
    int prevWidth = GetWidth( ), prevHeight = GetHeight( );
    
    SetWidth( width );
    
    SetHeight( height );
    
    if( !CreateDisplays( ) )
    {
	SetWidth( prevWidth );
	SetHeight( prevHeight );
    }
}



/**
 * Sets button's caption.
 *
 * @param caption: the desired caption
 */

void Button::SetCaption(const std::string &caption)
{
    //save previous caption
    std::string previousCaption = m_caption;

    //change caption
    m_caption = caption;

    //render displays with the new caption, if rendering fails
    if( !CreateDisplays( ) )
    {
        //revert caption change
        m_caption = previousCaption;
    }
}


/**
 * Sets the background color of the button.
 *
 * @param color: desired color
 */

void Button::SetBackgroundColor(const sdl::Color &color)
{
    std::auto_ptr< sdl::Canvas > tempDisplay;
    
    try
    {
        //create a temporary canvas with the desired color
        tempDisplay.reset( new sdl::Canvas( GetWidth( ), GetHeight( ), color ) );
        //render caption
        std::auto_ptr< sdl::TextImage > txt = m_font->GetImage( m_caption, m_colors[REGULAR][FOREGROUND] );
        //blit caption centered on the temporary canvas
        txt->Blit( ( GetWidth( ) / 2 ) - ( txt->GetWidth( ) / 2 ), ( GetHeight( ) / 2 ) - ( txt->GetHeight( ) / 2 ), tempDisplay.get( ) );
    }
    catch( sdl::Exception &exc )
    {
        //if an error occurrs return
        return;
    }

    //everything went smoothly
    //swap display with the temporary canvas
    std::swap( m_displays[REGULAR], tempDisplay );
    m_colors[REGULAR][BACKGROUND] = color;
}


/**
 * Sets the foreground color of the button.
 *
 * @param color: desired color
 */

void Button::SetForegroundColor(const sdl::Color& color)
{
    std::auto_ptr< sdl::Canvas > tempDisplay;

    try
    {   
        //create a temporary canvas
        tempDisplay.reset( new sdl::Canvas( GetWidth( ), GetHeight( ), m_colors[REGULAR][BACKGROUND] ) );
        //render caption
        std::auto_ptr< sdl::TextImage > txt = m_font->GetImage( m_caption, color );
        //blit caption centered on the temporary canvas
        txt->Blit( ( GetWidth( ) / 2 ) - ( txt->GetWidth( ) / 2 ), ( GetHeight( ) / 2 ) - ( txt->GetHeight( ) / 2 ), tempDisplay.get( ) );
    }    
    catch( sdl::Exception &exc )
    {
        //if an error occurrs return
        return;
    }

    //everything went smoothly
    //swap display with the temporary canvas
    std::swap( m_displays[REGULAR], tempDisplay );
    m_colors[REGULAR][FOREGROUND] = color;
}


/**
 * Sets the background color of the button when it's highlighted.
 *
 * @param color: desired color
 */

void Button::SetHighlightBackgroundColor(const sdl::Color &color)
{
    std::auto_ptr< sdl::Canvas > tempDisplay;

    try
    {
        //create a temporary canvas with the desired color
        tempDisplay.reset( new sdl::Canvas( GetWidth( ), GetHeight( ), color ) );
        //render caption
        std::auto_ptr< sdl::TextImage > txt = m_font->GetImage( m_caption, m_colors[HIGHLIGHTED][FOREGROUND] );
        //blit caption centered on the temporary canvas
        txt->Blit( ( GetWidth( ) / 2 ) - ( txt->GetWidth( ) / 2 ), ( GetHeight( ) / 2 ) - ( txt->GetHeight( ) / 2 ), tempDisplay.get( ) );

    }
    catch( sdl::Exception &exc )
    {
        //if an error occurrs return
        return;
    }

    //everything went smoothly
    //swap display with the temporary canvas
    std::swap( m_displays[HIGHLIGHTED], tempDisplay );
    m_colors[HIGHLIGHTED][BACKGROUND] = color;
}


/**
 * Sets the foreground color of the button when it's highlighted.
 *
 * @param color: desired color
 */

void Button::SetHighlightForegroundColor(const sdl::Color &color)
{
    std::auto_ptr< sdl::Canvas > tempDisplay;

    try
    {
        //create a temporary canvas
        tempDisplay.reset( new sdl::Canvas( GetWidth( ), GetHeight( ), m_colors[HIGHLIGHTED][BACKGROUND] ) );
        //render caption
        std::auto_ptr< sdl::TextImage > txt = m_font->GetImage( m_caption, color );
        //blit caption centered on the temporary canvas
        txt->Blit( ( GetWidth( ) / 2 ) - ( txt->GetWidth( ) / 2 ), ( GetHeight( ) / 2 ) - ( txt->GetHeight( ) / 2 ), tempDisplay.get( ) );
    }
    catch( sdl::Exception &exc )
    {
        //if an error occurrs return
        return;
    }

    //swap display with the temporary canvas
    std::swap( m_displays[HIGHLIGHTED], tempDisplay );
    m_colors[HIGHLIGHTED][FOREGROUND] = color;
}


/**
 * Sets the background color of the button when it's clicked.
 *
 * @param color: desired color
 */

void Button::SetClickedBackgroundColor(const sdl::Color &color)
{
    std::auto_ptr< sdl::Canvas > tempDisplay;

    try
    {
        //create a temporary canvas with the desired color
        tempDisplay.reset( new sdl::Canvas( GetWidth( ), GetHeight( ), color ) );
        //render caption
        std::auto_ptr< sdl::TextImage > txt = m_font->GetImage( m_caption, m_colors[CLICKED][FOREGROUND] );
        //blit caption centered on the temporary canvas
        txt->Blit( ( GetWidth( ) / 2 ) - ( txt->GetWidth( ) / 2 ), ( GetHeight( ) / 2 ) - ( txt->GetHeight( ) / 2 ), tempDisplay.get( ) );
        //swap display with the temporary canvas

    }
    catch( sdl::Exception &exc )
    {
        //if an error occurrs return
        return;
    }

    //swap display with the temporary canvas
    std::swap( m_displays[CLICKED], tempDisplay );
    m_colors[CLICKED][BACKGROUND] = color;
}

/**
 * Sets the foreground color of the button when it's clicked.
 *
 * @param color: desired color
 */

void Button::SetClickedForegroundColor(const sdl::Color &color)
{
    std::auto_ptr< sdl::Canvas > tempDisplay;

    try
    {
        //create a temporary canvas
        tempDisplay.reset( new sdl::Canvas( GetWidth( ), GetHeight( ), m_colors[CLICKED][BACKGROUND] ) );
        //render caption
        std::auto_ptr< sdl::TextImage > txt = m_font->GetImage( m_caption, color );
        //blit caption centered on the temporary canvas
        txt->Blit( ( GetWidth( ) / 2 ) - ( txt->GetWidth( ) / 2 ), ( GetHeight( ) / 2 ) - ( txt->GetHeight( ) / 2 ), tempDisplay.get( ) );
    }
    catch( sdl::Exception &exc )
    {
        //if an error occurrs return
        return;
    }

    //swap display with the temporary canvas
    std::swap( m_displays[CLICKED], tempDisplay );
    m_colors[CLICKED][FOREGROUND] = color;
}


/**
 * Renders the button on a surface depending on its state.
 *
 * @param target: target surface
 */

void Button::Draw(sdl::Surface* target) const
{   
    m_displays[m_currentDisplay]->Blit( GetX( ), GetY( ), target, &m_visiblePart );
}


/**
 * Updates the button.
 */

void Button::Update()
{
}


/**
 * This method is used by the button to render a new display if the text changes.
 * If a rendering operation fails the button retains its internal state since
 * changes are made to temporary objects, if all operations succeed then
 * the members of the button are swaped with the temporary ones.
 *
 * @return a boolean value indicating if display creation was successful.
 */

bool Button::CreateDisplays()
{
    std::auto_ptr< sdl::Canvas > tempDisplays[3];

    try
    {
        //for each type of display (REGULAR, HIGHLIGHTED, CLICKED)
        for( int displayType = REGULAR; displayType <= CLICKED; ++displayType )
        {
            //allocate a new temporary canvas with size the same as the button and same color
            //as the background
            tempDisplays[displayType].reset( new sdl::Canvas( GetWidth( ), GetHeight( ), m_colors[displayType][BACKGROUND] ) );
            //render caption
            std::auto_ptr< sdl::TextImage > txt = m_font->GetImage( m_caption, m_colors[displayType][FOREGROUND] );
            //blit caption on temporary canvas
            txt->Blit( ( GetWidth( ) / 2 ) - ( txt->GetWidth( ) / 2 ), ( GetHeight( ) / 2 ) - ( txt->GetHeight( ) / 2 ), tempDisplays[displayType].get( ) );
        }
    }
    catch( sdl::Exception &exc )
    {
        return false;
    }

    //since all rendering was successful, swap displays to the temporary displays
    for( int displayType = REGULAR; displayType <= CLICKED; ++displayType )
    {
        std::swap( m_displays[displayType], tempDisplays[displayType] );
    }

    return true;
}


/**
 * This routine initializes the button. If an operation fails then it returns
 * failure.
 *
 * @return a boolean value indicating if initialization was successful.
 */

bool Button::Initialize()
{
    //set the button's height the highest value between the desired height and the font's height
    SetHeight( std::max( m_font->GetHeight( ), GetHeight( ) ) );

    //get the size of the caption in pixels
    int textWidth, textHeight;
    m_font->TextSize( m_caption, textWidth, textHeight );
    //set the button's width the highest value between the desired width and the text's width
    SetWidth( std::max( textWidth, GetWidth( ) ) );


    //initialize default colors
    m_colors[REGULAR][BACKGROUND] = sdl::Colors::GRAY;
    m_colors[REGULAR][FOREGROUND] = sdl::Colors::BLACK;
    m_colors[HIGHLIGHTED][BACKGROUND] = sdl::Colors::BLACK;
    m_colors[HIGHLIGHTED][FOREGROUND] = sdl::Colors::GRAY;
    m_colors[CLICKED][BACKGROUND] = sdl::Colors::BLACK;
    m_colors[CLICKED][FOREGROUND] = sdl::Colors::WHITE;

    m_clicked = false;

    //create displays, if something fails
    if( !CreateDisplays( ) )
    {
        //return failure
        return false;
    }

    //return success
    return true;
}

};
